#include <AppModel/Include/AppLogic.h>

#include <Render/Include/RendererDX9.h>
#include <Render/Include/RendererDX11.h>

using namespace AppModel;

// ========== LIFECYCLE ========== //

AppLogic::AppLogic(Render::IWinWindow* window,  const RenderingPlatform &platform)
{
    // this->renderer = new Render::RendererDX11(window);
    this->window = window;
    switch (platform)
    {
    case RenderingPlatform::RENDER_WITH_DX9:
        this->renderer = new Render::RendererDX9(window);
        break;
    case RenderingPlatform::RENDER_WITH_DX11:
        this->renderer = new Render::RendererDX11(window);
        break;
    default:
        throw -1;
    }
    this->current_rendering_platform = platform;
    
    this->initializeTorus(true);

    this->time_meter = new TimeMeter();
    
    this->camera_theta = 0.0f;
    this->camera_phi = 0.0f;
    this->camera_radio = 10.0f;
}


AppLogic::~AppLogic(void)
{
    Render::Mesh* torus_mesh = torus->getMesh();
    delete torus_mesh;
    Render::Texture* torus_texture = torus->getTexture();
    delete torus_texture;
    delete this->torus;
    delete this->renderer;
    delete this->time_meter;
}

// ========== OVERLODED OPERATORS ========== //

// ========== METHODS ========== //
void AppLogic::run()
{
    this->wait_stop_task_semaphore = new Semaphore();
    this->task = new Task(this->main_task_function, this, T_TaskPriority::TASK_PRIORITY_ABOVE_NORMAL, TASK_TIME_STEP, this->wait_stop_task_semaphore);
    this->time_meter->start();
    this->task->start();
}

void AppLogic::stop()
{
    this->task->stop();
    this->wait_stop_task_semaphore->wait();
    delete this->wait_stop_task_semaphore;
}

void AppLogic::input(const AppInputType &input_type)
{
    bool config_camera = true;
    switch (input_type)
    {
    case AppInputType::LEFT_ARROW_PRESSED:
        camera_theta -= ANGLE_STEP;
        break;
    case AppInputType::RIGHT_ARROW_PRESSED:
        camera_theta += ANGLE_STEP;
        break;
    case AppInputType::UP_ARROW_PRESSED:
        camera_phi += ANGLE_STEP;
        break;
    case AppInputType::DOWN_ARROW_PRESSED:
        camera_phi -= ANGLE_STEP;
        break;
    case AppInputType::CONTROL_KEY_PRESSED:
        camera_radio += RADIO_STEP;
        break;
    case AppInputType::SHIFT_KEY_PRESSED:
        camera_radio -= RADIO_STEP;
        break;
    case AppInputType::SPACE_BAR_PRESSED:
        config_camera = false;
        this->switchRenderDevices();
        break;
    }

    if (config_camera)
    {
        this->renderer->configArcBallCamera(this->camera_theta, this->camera_phi, this->camera_radio);
    }
    
}

// ========== GETTERS ========== //

// ========== SETTERS ========== //

// ========== INQUIRY ========== //

// ========== INTERNAL METHODS ========== //
void AppLogic::switchRenderDevices()
{
    this->stop();

    Render::Mesh* torus_mesh = torus->getMesh();
    delete torus_mesh;
    Render::Texture* torus_texture = torus->getTexture();
    delete torus_texture;
    delete this->renderer;

    switch (this->current_rendering_platform)
    {
    case RenderingPlatform::RENDER_WITH_DX9:
        this->renderer = new Render::RendererDX11(window);
        this->current_rendering_platform = RenderingPlatform::RENDER_WITH_DX11;
        break;
    case RenderingPlatform::RENDER_WITH_DX11:
        this->renderer = new Render::RendererDX9(window);
        this->current_rendering_platform = RenderingPlatform::RENDER_WITH_DX9;
        break;    
    }
    this->initializeTorus(false);
    this->run();
    this->renderer->configArcBallCamera(this->camera_theta, this->camera_phi, this->camera_radio);    
}

void AppLogic::initializeTorus(bool fisrt_time)
{
    unsigned int rings = 50;
    unsigned int segments = 30;
    float outter_radio = 4.0f;
    float inner_radio = 1.0f;
    unsigned int vertex_count = rings * segments;
    Render::Renderer::VertexData* vertices = new Render::Renderer::VertexData[vertex_count];

    float M_PI = 3.14159f;

    float delta_beta = 2.0f * M_PI / (float)(segments-1);
    float delta_alpha = 2.0f * M_PI / (float)(rings-1);

    unsigned int vertex_pos = 0;
    float alpha = 0.0f;
    for (unsigned int i = 0; i < rings; i++ )
    {
        float beta = 0.0f;
        for (unsigned int j = 0; j < segments; j++ )
        {
            vertices[vertex_pos].position_x = (outter_radio - inner_radio * cos(beta)) * cos(alpha);
            vertices[vertex_pos].position_y = (outter_radio - inner_radio * cos(beta)) * sin(alpha);
            vertices[vertex_pos].position_z = inner_radio * sin(beta);

            vertices[vertex_pos].normal_x = - cos(alpha) * cos(beta);
            vertices[vertex_pos].normal_y = - sin(alpha) * cos(beta);
            vertices[vertex_pos].normal_z = sin(beta);

            vertices[vertex_pos].tex_coords_u = beta / (2.0f * M_PI);
            vertices[vertex_pos].tex_coords_v = alpha / (2.0f * M_PI);

            beta += delta_beta;
            vertex_pos++;
        }
        alpha += delta_alpha;
    }

    unsigned int index_count = segments * rings * 2 * 3;
    Render::Renderer::IndexData* indices = new Render::Renderer::IndexData[index_count];
    unsigned int index_pos = 0;

    for (unsigned int i = 0; i < rings; i++ )
    {
        for (unsigned int j = 0; j < segments; j++ )
        {
            indices[index_pos++] = i * segments + j;
            indices[index_pos++] = i * segments + ( (j + 1) % segments );
            indices[index_pos++] = ((i + 1) % rings ) * segments + ( (j + 1) % segments );

            indices[index_pos++] = i * segments + j;
            indices[index_pos++] = ((i + 1) % rings ) * segments + ( (j + 1) % segments );
            indices[index_pos++] = ((i + 1) % rings ) * segments + j; 
        }
    }

    Render::Mesh* torus_mesh = this->renderer->createMesh(vertices, vertex_count, indices, index_count);

    delete indices;
    delete vertices;

    // Render::Texture* torus_texture = this->renderer->createTexture("groundstones.jpg");
    Render::Texture* torus_texture = this->renderer->createTexture("water.jpg");

    if( fisrt_time )
    {
        this->torus = new Torus(torus_mesh, torus_texture);
    }
    else
    {
        this->torus->changeResources(torus_mesh, torus_texture);
    }    
}

void AppLogic::main_task_function(void* user_data)
{
    AppLogic* app_logic = reinterpret_cast<AppLogic*>(user_data);
    float time_elapsed_ms = (float)app_logic->time_meter->end();
    app_logic->time_meter->start();

    app_logic->torus->progress(time_elapsed_ms);
    app_logic->renderer->renderTorus(app_logic->torus);
}
